package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"time"

	"github.com/go-redis/redis"
	log "github.com/sirupsen/logrus"
)

type Duration time.Duration

func (d Duration) MarshalJSON() ([]byte, error) {
	return json.Marshal(fmt.Sprintf("%s", time.Duration(d)))
}

func (d *Duration) UnmarshalJSON(b []byte) error {
	var v interface{}
	if err := json.Unmarshal(b, &v); err != nil {
		return err
	}
	switch value := v.(type) {
	case float64:
		*d = Duration(time.Duration(value))
		return nil
	case string:
		tmp, err := time.ParseDuration(value)
		if err != nil {
			return err
		}
		*d = Duration(tmp)
		return nil
	default:
		return errors.New("invalid duration")
	}
}

func (d Duration) ToDuration() time.Duration {
	return time.Duration(d)
}

type CacheConfig struct {
	URI              string   `json:"uri,omitempty"`
	DB               int      `json:"db,omitempty"`
	KeyPrefix        string   `json:"keyPrefix,omitempty"`
	OnlineKeyPrefix  string   `json:"onlineKeyPrefix,omitempty"`
	OnlineExpiration Duration `json:"onlineExpiration,omitempty"`
}

type Cache struct {
	config CacheConfig
	client *redis.Client
}

func NewCache(config CacheConfig) (*Cache, error) {
	opts := &redis.Options{
		Addr: config.URI,
		DB:   config.DB,
	}
	client := redis.NewClient(opts)
	_, err := client.Ping().Result()
	if err != nil {
		return nil, err
	}
	return &Cache{
		config: config,
		client: client,
	}, nil
}

func formatTimestamp(t time.Time) string {
	return t.Format(time.RFC3339)
}

func (c *Cache) Set(id string, values map[string]interface{}) error {
	key := fmt.Sprintf("%s%s", c.config.KeyPrefix, id)
	onlineKey := fmt.Sprintf("%s%s", c.config.OnlineKeyPrefix, id)
	vs := map[string]interface{}{}
	for k, v := range values {
		vs[k] = v
	}
	vs["updatedAt"] = formatTimestamp(time.Now())
	pipe := c.client.TxPipeline()
	pipe.HMSet(key, vs)
	pipe.SetNX(onlineKey, formatTimestamp(time.Now()), c.config.OnlineExpiration.ToDuration())
	pipe.Expire(onlineKey, c.config.OnlineExpiration.ToDuration())
	cmds, err := pipe.Exec()
	if err != nil {
		log.WithFields(log.Fields{"err": err, "cmd": cmds}).Error("failed update cache")
	} else {
		log.WithFields(log.Fields{"cmd": cmds}).Info("successfully update cache")
	}
	return err
}

// func (c *Cache) StartRefresher() {
// 	opts := &redis.Options{
// 		Addr: c.config.URI,
// 		DB:   c.config.DB,
// 	}
// 	subC := redis.NewClient(opts)
// 	onlineKey := fmt.Sprintf("%s%s", c.config.onlineKeyPrefix, "*")
// 	channel := fmt.Sprintf("__keyspace@%d__:%s", c.config.DB, onlineKey)
// 	sub := subC.PSubscribe(channel)
// 	ctx, cancel := context.WithCancel(context.Background())
// 	for {
// 		select {
// 		case <-ctx.Done():
// 			{
// 				break
// 			}
// 		case msg := <-sub.Channel():
// 			{
// 				if msg.Payload != "del" {
// 					break
// 				}
// 				parts := strings.Split(msg.Channel, ":")
// 				if len(parts) < 1 {
// 					log.WithFields(log.Fields{"msg": msg}).Warn("malformed message")
// 					break
// 				}
// 				id := parts[len(parts)-1]
// 				break
// 			}
// 		}
// 	}
// }
